{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Modules and Variable Scope" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modules" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "One of the strengths of Python is that there are many built-in add-ons - or *modules* - which contain existing functions, classes, and variables which allow you to do complex tasks in only a few lines of code. In addition, there are many other third-party modules (e.g. Numpy, Scipy, Matplotlib) that can be installed, and you can also develop your own modules that include functionalities you commonly use." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "The built-in modules are referred to as the *Standard Library*, and you can find a full list of the available functionality in the [Python Documentation](http://docs.python.org/3/library/index.html)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "To use modules in your Python session or script or functions, you need to **import** them. The following example shows how to import the built-in ``math`` module, which contains a number of useful mathematical functions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "You can then access functions and other objects in the module with ``math.``, for example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math.sin(2.3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math.factorial(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math.pi" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Because these modules exist, it is often the case that there are already modules and functions that can do what you want to do. This will make your code much more readable and efficient." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "For example, the ``numpy`` module, which we will talk about tomorrow, contains useful functions for finding e.g. the mean, median, and standard deviation of a sequence of numbers:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "li = [1,2,7,3,1,3]\n", "np.mean(li)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "np.median(li)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.std(li)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "li=[1,2,3]\n", "print(np.std(li,ddof=1), np.sqrt((1+0+1)/2)) # Bessel factor" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.std?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Notice that in the above case, we used:\n", "\n", " import numpy as np\n", " \n", "instead of:\n", "\n", " import numpy\n", " \n", "which shows that we can rename the module such that it is more convenient to access the module's variables, functions etc." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Finally, it's also possible to simply import the functions needed directly:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from math import sin, cos\n", "print(sin(3.4))\n", "print(cos(3.4))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "You may find examples on the internet that use e.g.\n", "\n", " from module import *\n", " \n", "but this is **not** recommended, because it will make it difficult to debug programs, since common debugging tools that rely on just looking at the programs will not know all the functions that are being imported." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Where to find modules and functions" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "How do you know which modules exist in the first place? The Python documentation contains a [list of modules in the Standard Library](http://docs.python.org/3/library), but you can also simply search the web. Once you have a module that you think should contain the right kind of function, you can either look at the documentation for that module, or you can use the tab-completion in IPython:\n", " \n", " In [2]: math.\n", " math.acos math.degrees math.fsum math.pi\n", " math.acosh math.e math.gamma math.pow\n", " math.asin math.erf math.hypot math.radians\n", " math.asinh math.erfc math.isinf math.sin\n", " math.atan math.exp math.isnan math.sinh\n", " math.atan2 math.expm1 math.ldexp math.sqrt\n", " math.atanh math.fabs math.lgamma math.tan\n", " math.ceil math.factorial math.log math.tanh\n", " math.copysign math.floor math.log10 math.trunc\n", " math.cos math.fmod math.log1p \n", " math.cosh math.frexp math.modf " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Does the ``math.cos`` funtion take radians or degrees?\n", "\n", "* Search for functions that can convert between radians and degrees.\n", "\n", "* Use these to find the cosine of 60 degrees, and the sine of $\\pi/6$ radians." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "# enter your solutions here\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variable scope" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Local scope" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, the variables defined in the function are not available outside the function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def do_something():\n", " a = 1\n", "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The variable ``a`` is defined in the **local scope** of the function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Global scope" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Consider the following example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = 1\n", "\n", "def show_var():\n", " print(a, b)\n", "\n", "b = 2\n", "show_var()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, the function knows about the variables defined outside the function. The variables are in the **global scope**. This is very useful because it means that modules don't have to be imported inside functions, you can import them at the top level:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "def normalize(x):\n", " return x / np.mean(x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a=[5,3,5,7,8,3]\n", "normalize(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This works because modules are objects in the same sense as any other variable. In practice, this does **not** mean that you should ever use:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = 1\n", "def show_var(x):\n", " print(x)\n", " \n", "show_var(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "because it makes the code harder to read. The exception to this are modules and variables that remain constant during the execution of the program. One exception to this is if you need to define constants (such as pi, or physical constants). See the PEP8 section below for more details." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Local scope has precedence over global scope" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Consider the following example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = 1\n", "\n", "def show_var():\n", " a = 2\n", " print(a)\n", "\n", "show_var()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What happened? Variables defined anywhere inside a function are part of the **local scope** of the function. Any variable in the local scope takes precedence over any other variable, **even before it is actually used**:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def show_var():\n", " print(a)\n", " a = 2\n", " \n", "show_var()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, ``a`` is defined inside the function and so it doesn't matter if a is used anywhere else in the Python code. The above function will therefore not work because ``a`` is used before it is defined." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What will the following code print? (think about it, don't run it!):\n", "\n", " def double(x):\n", " x = x * 2\n", " print(x)\n", "\n", " x = 1\n", " double(x)\n", " print(x)\n", " \n", "and what about this code?:\n", "\n", " def append_3(x):\n", " x.append(3)\n", " print(x)\n", "\n", " x = [1,2]\n", " append_3(x)\n", " print(x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ok, you can try them out now!\n", "def double(x):\n", " x = x * 2\n", " print(x)\n", "\n", "x = 1\n", "double(x)\n", "print(x)\n", "\n", "\n", "def append_3(x):\n", " x.append(3)\n", " print(x)\n", "\n", "x = [1,2]\n", "append_3(x)\n", "print(x)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## PEP8 style" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "[=Python Enhancement Proposal 8]\n", "\n", "We just touched on the idea of constants being used in functions - but Python does not *really* have constants, so how do we recognize them? We now need to speak about coding style." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "There is a set of style guidelines referred to as PEP8, which you can find [here](http://www.python.org/dev/peps/pep-0008/). These guidelines are not compulsory, but you should follow them as much as possible, especially when you have to work with other people or need other people to read your code." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "You don't need to read the guidelines now, but I will first give a couple of examples, then I will show you a tool that can help you follow the guidelines. The following example does not follow the style guidelines:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pi = 3.1415926\n", "def CalculateValues(x):\n", " return(x*pi)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Constants should be made uppercase, and function names should be lower case separated by underscores (the so called *camel-case* used above is reserved for classes)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "This is the correct way to write the code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "PI = 3.1415926\n", "\n", "def calculate_values(x):\n", " return(x * PI)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Other examples include that indentation should always be 4 spaces, etc. In practice, you can check your code with [this script](https://github.com/PyCQA/pycodestyle) although this does not work in the notebook. Download the ``pycodestyle.py`` script from Github and upload it to your working folder. Then also create a Python script called ``my_script.py`` locally with the following content:\n", "\n", " #!/usr/bin/env python\n", "\n", " # This is a very long comment that will be much longer than the desired linelength of 79 chars\n", "\n", " def do_something():\n", " print ('We are doing something...')\n", "\n", " do_something()\n", "\n", "and upload it to the same folder on the Jupyter Server. Go to the overview of all your files on the Jupyter Server and select \"Terminal\" from the \"New\" menu item. A new Browser tab with an open terminal wll appear. In this terminal, run:\n", "\n", " python pycodestyle.py my_script.py\n", " \n", "where ``my_script.py`` is the script you just created and that you want to check. In the above example, you will see:\n", "\n", " my_script.py:3:80: E501 line too long (94 > 79 characters)\n", " my_script.py:5:1: E302 expected 2 blank lines, found 1\n", " my_script.py:6:4: E111 indentation is not a multiple of four\n", " my_script.py:6:9: E211 whitespace before '('\n", " my_script.py:8:1: E305 expected 2 blank lines after class or function definition, found 1\n", " my_script.py:8:15: W292 no newline at end of file\n", " \n", "The errors include the line number and you can adjust your code according to PEP 8." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One additional point about PEP 8:\n", "\n", "Make sure your code is never longer than 79 characters (with 72 characters a desirable limit).\n", "\n", "Why?\n", "\n", "1. 80 characters is about what the average human can understand “at a glance”, without having to visually follow lines and such. That's why books are printed the way they are. And why xterms and such should be 80 columns wide.\n", "2. When using version control, lines of that size still make useful diffs (i.e. changes from one version to another). Have longer lines, and you have lots of spurious material people have to scan to find what's actually changed.\n", "\n", "This is true not only for python source code. Make it a habit for TeX, markdown etc." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 }